//
//  TaurusDBModel.swift
//  SwiftLibrary
//
//  Created by wumin on 15/12/31.
//  Copyright © 2015年 taurusxi. All rights reserved.
//

import UIKit

//创建 信息表

class TaurusDBModel: NSObject {
  
  var columeNameArray = [String]()
  var columeTypeArray = [String]()
  
  required override init() {
    super.init()
    var dict = self.classForCoder.getPropertys()
    columeNameArray = dict["name"]!
    
    columeTypeArray = dict["type"]!
  }
  
  override class func initialize() {
//    printDBLog("self:\(self)__initialize")
    let start = CACurrentMediaTime()
    self.createTable()
    printDBLog("createtableTime:\(CACurrentMediaTime()-start)")
  }
  
  class func getPropertys() -> [String: Array<String>] {
    var proNames = [String]()
    var types = [String]()
    let transientsArray = self.transients()
    
    var outCount: UInt32 = 0;
    let properties = class_copyPropertyList(self, &outCount)
    
    for index in 0..<(Int(outCount)) {
      
      let property = properties?[index]
      let propertyName: String = NSString(cString: property_getName(property), encoding: String.Encoding.utf8.rawValue)! as String
      
      if transientsArray.contains(propertyName) {
        continue
      }
      
      proNames.append(propertyName)
      
      let propertyType: String = NSString(cString: property_getAttributes(property), encoding: String.Encoding.utf8.rawValue)! as String
      
      // T@ NSString  Text类型
      // TB,N Bool类型 用 Interger存储
      // Td,N Double 类型，用 real 存储
      // Tq.N Int类型,用 Interger 存储
      if propertyType .hasPrefix("T@") {
        types.append(SQLTEXT)
      } else if (propertyType.hasPrefix("TB") || propertyType.hasPrefix("Tq")) {
        types.append(SQLINTEGER)
      } else if propertyType.hasPrefix("Td") {
        types.append(SQLREAL)
      }
    }
    free(properties)
    var result = [String : Array<String>]()
    result["name"] = proNames
    result["type"] = types
    return result
  }
  
  class func getAllProperties() -> [String : Array<String>] {
    
    let dic = self.getPropertys()
    
    return dic
  }
  // 18310602975
  
  class func isExistInTable() -> Bool {
    
    var isExistFlag = false
    let helper = TaurusDbHelper.sharedInstance
    helper.dbQueue?.inDatabase {
      fmDatabase in
      let tableName = getTableName(self)
      isExistFlag = fmDatabase!.tableExists(tableName)
    }
    return isExistFlag
  }
  
  class func getColumns() -> [String] {
    
    var columns = [String]()
    
    let tableName = getTableName(self)
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      guard let db = db else {
        return
      }

      if let resultSet = db.getTableSchema(tableName) {
        while resultSet.next() {
          columns.append(resultSet.string(forColumn: "name"))
        }
      }
      
    }
    
    return columns
  }
  
  class func createTable() -> Bool {
    
    var flag = true
    let tableName = getTableName(self)
    if tableName == "SWIFTLIBRARY_TAURUSDBMODEL" {
      return flag
    }
    
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      guard let db = db else {
        return
      }

      let sql = "CREATE TABLE IF NOT EXISTS \(tableName) ( \(self.getColumeAndTypeString()) ); "
      
      printDBLog("create_SQL:\(sql)")
      
      let tableExist = db.executeUpdate(sql) ;
      
      if tableExist {
        self.saveVersionNumber(db)
      } else {
        flag = false
//        rollback.memory = true
        return
      }
      
      var flag = self.isNeedForAlter(db, version: self.getVersion())
      
      if !flag {
        return
      }
      
      printDBLog("需要迁移表")
      
      let resultSet_Option = db.getTableSchema(tableName)
      
      guard let resultSet = resultSet_Option else {
        return
      }

      var columns = [AnyObject]()
      while resultSet.next() {
        columns.append(resultSet.string(forColumn: "name") as AnyObject)
      }
      
      var dict = self.getAllProperties()
      let properties = dict["name"]
      
      let filterPerdicate = NSPredicate(format: "NOT (SELF IN %@)", argumentArray: columns)
      
      let resultArray = (properties! as NSArray).filtered(using: filterPerdicate)
      
      for column in resultArray {
        
        let index = properties?.index(of: column as! String)
        var type = ""
        if let array: [String] = dict["type"] {
          type = array[Int(index!)]
        }
        
        let sql = "ALTER TABLE \(tableName) ADD COLUMN \(column) \(type)"
        printDBLog("alter table :\(sql)")
        do {
          try db.executeUpdate(sql, values: nil)
        } catch let error {
          flag = false
          printDBLog("error:\(error)")
//          rollback.memory = true
          return
        }
      }
    }
    printDBLog("createTable:\(tableName)__\(flag)")
    return flag
  }
  
  func saveOrUpdate() -> Bool {
    
    if isExistModel() {
      return self.update()
    } else {
      return self.save()
    }
  }
  
  //MARK: 需要修改
  func isExistModel() -> Bool {
    let start = CACurrentMediaTime()
    let primaryKeyOption = self.value(forKey: type(of: self).getPrimarayKey()) as? String
    guard let primaryKey = primaryKeyOption else {
      return false
    }
    let resultOption = type(of: self).findByPrimaryKey(stringPK: primaryKey)
    
    printDBLog("isModelExist:\(CACurrentMediaTime()-start)")
    if let _ = resultOption {
      return true
    } else {
      return false
    }
  }
  
  class func deleteList(_ modelList: [TaurusDBModel]?) -> Bool {
    
    guard let modelArray = modelList else {
      return true
    }
    
    let tableName = getTableName(self)
    let primaryKey = self.getPrimarayKey()
    
    var flag = true
    TaurusDbHelper.sharedInstance.dbQueue?.inTransaction {
      db, rollback in
      
      guard let db = db else {
        return
      }
      
      for model in modelArray {
        let sql = "DELETE FROM \(tableName) WHERE \(primaryKey) = '\(String(describing: model.value(forKey: primaryKey)!))'"
        
        flag = db.executeUpdate(sql)
        if (!flag) {
          printDBLog("deleteSql失败:\(sql)")
          rollback?.pointee = true
//          flag = false
          return
        }
      }
    }
    
    return flag
  }
  
  func delete() -> Bool {
    var flag = false
    if isExistModel() {
      
      TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
        db in
        guard let db = db else {
          return
        }

        let clazz = type(of: self)
        let primaryKey = clazz.getPrimarayKey()
        let primaryKeyValueOption = self.value(forKey: primaryKey)
        
        guard let primaryKeyValue = primaryKeyValueOption else {
          flag = false
          return
        }
        
        let index = self.columeNameArray.index(of: primaryKey)!
        let primaryType = self.columeTypeArray[index]
        
        var sql: String
        if primaryType == SQLINTEGER {
          sql = "DELETE FROM \(getTableName(clazz)) WHERE \(primaryKey) = \(primaryKeyValue)"
        } else {
          sql = "DELETE FROM \(getTableName(clazz)) WHERE \(primaryKey) = '\(primaryKeyValue)'"
        }
        
        flag = db.executeUpdate(sql)
      }
    }
    return flag
  }
  
  func save() -> Bool {
    
    var flag = false
    TaurusDbHelper.sharedInstance.dbQueue?.inTransaction {
      db, rollback in
      guard let db = db else {
        return
      }

      let primaryValueOption = self.value(forKey: type(of: self).getPrimarayKey())
      
      guard let pk = primaryValueOption else {
        return
      }
      
      var keyString = ""
      var valueStr = ""
      var insertValues = ""
      
      for i in 0 ..< self.columeNameArray.count  {
        
        let columeName = self.columeNameArray[i]
        if (columeName == pk as! String) {
          continue
        }
        keyString += " \(columeName)"
        valueStr += "?"
        
        var value = self.value(forKey: columeName)
        if value == nil {
          value = ""
        }
        
        if let valueStr = value as? String {
          insertValues += "'\(valueStr)'"
        } else {
          insertValues += String(describing: value!)
        }
        
        if (i != self.columeNameArray.count - 1) {
          keyString += ","
          valueStr += ","
          insertValues += ","
        }
      }
      let sql = "INSERT INTO \(getTableName(type(of: self))) (\(keyString)) VALUES (\(insertValues));"
      printDBLog("insertSQL:\(sql)")
      
      flag = db.executeUpdate(sql)
    }
    return flag
  }
  
  func update() -> Bool {
    
    var flag = false
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      guard let db = db else {
        return
      }

      let primaryKey = type(of: self).getPrimarayKey() ;
      let primaryValueOption = self.value(forKey: primaryKey)
      
      guard let pk = primaryValueOption else {
        return
      }
      
      var keyString = ""
      let count = self.columeNameArray.count
      for i in 0 ..< count  {
        
        let columeName = self.columeNameArray[i]
        let columeType = self.columeTypeArray[i]
        if (columeName == primaryKey) {
          continue
        }
        
        var value = self.value(forKey: columeName)
        if let _ = value {
        } else {
          value = ""
        }
        
        if columeType == SQLTEXT {
          keyString += " \(columeName) = '\(value!)'"
        } else {
          keyString += " \(columeName) = \(value!)"
        }
        
        if (i != count - 1) {
          keyString += ","
        }
      }
      
      let sql = "UPDATE \(getTableName(type(of: self))) SET \(keyString) WHERE \(primaryKey) = '\(pk)';"
      printDBLog("updateSql:\(sql)")
      flag = db.executeUpdate(sql)
    }
    
    return flag
  }
  
  class func deleteAll() -> Bool {
    
    var flag = false
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      guard let db = db else {
        return
      }

      flag = db.executeUpdate("DELETE FROM \(getTableName(self))")
    }
    return flag
  }
  
  class func findAll() -> [TaurusDBModel] {
    
    var results = [TaurusDBModel]()
    
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      
      guard let db = db else {
        return
      }
      
      let fmResultSetOption = db.executeQuery("SELECT * FROM \(getTableName(self))")
      
      guard let fmResultSet = fmResultSetOption else {
        return
      }
      
      while fmResultSet.next() {
        let model = self.init()
        for i in 0 ..< model.columeNameArray.count {
          let columeName = model.columeNameArray[i]
          let columeType = model.columeTypeArray[i]
          
          let index = Int32(i)
          if columeType == SQLTEXT {
            model.setValue(fmResultSet.string(forColumnIndex: index)!, forKey: columeName)
          } else if columeType == SQLINTEGER {
            model.setValue(NSNumber(value: fmResultSet.longLongInt(forColumnIndex: index) as Int64), forKey: columeName)
          } else if columeType == SQLREAL {
            model.setValue(NSNumber(value: fmResultSet.double(forColumnIndex: index) as Double), forKey: columeName)
          }
        }
        results.append(model)
      }
    }
    return results
  }
  
  class func findAll(WithDescFlag flag:Bool) -> [TaurusDBModel] {
    
    var results = [TaurusDBModel]()
    
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      
      guard let db = db else {
        return
      }
      
      
      let sql = "SELECT * FROM \(getTableName(self))" + " order by \(self.getPrimarayKey()) \(flag ? "desc" : "asc")"
      
      let fmResultSetOption = db.executeQuery(sql)
      
      guard let fmResultSet = fmResultSetOption else {
        return
      }
      
      while fmResultSet.next() {
        let model = self.init()
        for i in 0 ..< model.columeNameArray.count {
          let columeName = model.columeNameArray[i]
          let columeType = model.columeTypeArray[i]
          
          let index = Int32(i)
          if columeType == SQLTEXT {
            model.setValue(fmResultSet.string(forColumnIndex: index)!, forKey: columeName)
          } else if columeType == SQLINTEGER {
            model.setValue(NSNumber(value: fmResultSet.longLongInt(forColumnIndex: index) as Int64), forKey: columeName)
          } else if columeType == SQLREAL {
            model.setValue(NSNumber(value: fmResultSet.double(forColumnIndex: index) as Double), forKey: columeName)
          }
        }
        results.append(model)
      }
    }
    return results
  }
  
  class func findByPrimaryKey(stringPK pk: String) -> TaurusDBModel? {
    
    let primaryKey = self.getPrimarayKey()
    let sql = "WHERE \(primaryKey) = '\(pk)'"
    
    return findFirstByCriteria(sql)
  }
  
  class func findByPrimaryKey(intPk pk: Int) -> TaurusDBModel? {
    
    let primaryKey = self.getPrimarayKey()
    
    let sql = "WHERE \(primaryKey) = \(pk)"
    
    return findFirstByCriteria(sql)
  }
  
  class func findFirstByCriteria(_ criteria: String) -> TaurusDBModel? {
    
    let result = findByCriteria(criteria)
    
    if result.count < 1 {
      return nil
    }
    
    return (result.first! as TaurusDBModel)
  }
  
  class func findByCriteria(_ criteria: String) -> [TaurusDBModel] {
    
    var result = [TaurusDBModel]()
    
    TaurusDbHelper.sharedInstance.dbQueue?.inDatabase {
      db in
      
      
      guard let db  = db else {
        return
      }
      
      let sql = "SELECT * FROM \(getTableName(self)) \(criteria)"
      let fmResultSetOption = db.executeQuery(sql)
      
      guard let fmResultSet = fmResultSetOption else {
        return
      }
      
      while fmResultSet.next() {
        let model = self.init()
        for i in 0 ..< model.columeNameArray.count {
          let columeName = model.columeNameArray[i]
          let columeType = model.columeTypeArray[i]
          
          if columeType == SQLTEXT {
            model.setValue(fmResultSet.string(forColumn: columeName), forKey: columeName)
          } else {
            model.setValue(NSNumber(value: (fmResultSet.longLongInt(forColumn: columeName)) as Int64), forKey: columeName)
          }
        }
        result.append(model)
      }
    }
    
    return result
  }
  
  class func getColumeAndTypeString() -> String {
    
    var dict = self.getPropertys()
    var namesArray = dict["name"]
    var typesArray = dict["type"]
    
    let count = (namesArray?.count)!
    
    let primaryKey = self.getPrimarayKey()
    var params = ""
    for i in 0..<count {
      
      params += namesArray![i] + " "
      params += typesArray![i] + " "
      
      if namesArray![i] == primaryKey {
        params += PRIMARY_KEY
      }
      if i != (count - 1) {
        params += ","
      }
    }
    
    return params
  }
  
  class func transients() -> Array<String> {
    
    return ["columeNameArray", "columeTypeArray", "description", "debugDescription"]
  }
  
  class func getPrimarayKey() -> String {
    return "primarayKey"
  }
  
  class func getVersion() -> Int {
    return 1
  }
  
  class func isNeedForAlter(_ db: FMDatabase, version: Int) -> Bool {
    
    var flag = true;
    do {
      let resultSet = try db.executeQuery(SELECT_VERSION_SQL + "'\(getTableName(self))'", values: nil)
      var v = -1;
      while resultSet.next() {
        v = resultSet.long(forColumn: "version")
      }
      if v == version {
        flag = false
      }
    } catch {
    }
    
    return flag
  }
  
  class func saveVersionNumber(_ db: FMDatabase) {
    do {
      let resultSet = try db.executeQuery(SELECT_VERSION_SQL + "'\(getTableName(self))'", values: nil)
      var v = -1;
      while resultSet.next() {
        v = resultSet.long(forColumn: "version")
      }
      if v == -1 {
        db.executeUpdate(INSERT_VERSION_SQL + " ( '\(getTableName(self))' ,\(self.getVersion()))")
      }
    } catch {
    }
  }
}

extension TaurusDBModel {
  
  override var description: String {
    
    let count = columeNameArray.count
    
    var result = "{ "
    for index in 0..<count {
      
      let type = columeTypeArray[index]
      let key = columeNameArray[index]
      if type == SQLTEXT {
        result += "'\(key)':'\(String(describing: self.value(forKey: key)!))' "
      } else {
        result += "'\(key)':\(String(describing: self.value(forKey: key)!)) "
      }
      if index != (count - 1) {
        result += ","
      }
    }
    
    result += "}"
    return result
  }
  
}
